package com.wss.module.main.ui.view.ca;

import com.orhanobut.logger.Logger;
import com.wss.common.base.BaseApplication;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.graphics.Surface;
import ohos.agp.graphics.SurfaceOps;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.media.camera.CameraKit;
import ohos.media.camera.device.*;
import ohos.media.camera.params.PropertyKey;
import ohos.media.image.common.ImageFormat;
import ohos.media.image.common.Size;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PICTURE;
import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PREVIEW;

public class SenseCamera {

    public static final int CAMERA_FACING_BACK = CameraInfo.FacingType.CAMERA_FACING_BACK;

    public static final int CAMERA_FACING_FRONT = CameraInfo.FacingType.CAMERA_FACING_FRONT;

    private static final int DUMMY_TEXTURE_NAME = 100;

    private static final float ASPECT_RATIO_TOLERANCE = 0.01f;

    private Context mContext;

    private final Object mCameraLock = new Object();

    private Camera mCamera;

    private int mFacing = CAMERA_FACING_BACK;

    private int mRotation;

    private Size mPreviewSize = new Size(640, 480);

    private float mRequestedFps = 120.0f;
    private int mRequestedPreviewWidth = 640;
    private int mRequestedPreviewHeight = 480;

    private Map<byte[], ByteBuffer> mBytesToByteBuffer = new HashMap<>();

//    private Camera.PreviewCallback mPreviewCallback;

    public static class Builder {
        private final SenseCamera mSenseCamera = new SenseCamera();

        /**
         * Camera Builder.
         */
        public Builder(final Context context) {
            if (context == null) {
                throw new IllegalArgumentException("No context supplied.");
            }
            this.mSenseCamera.mContext = context;
        }

        /**
         * set requested fps.
         */
        public Builder setRequestedFps(final float fps) {
            if (fps <= 0) {
                throw new IllegalArgumentException("Invalid fps: " + fps);
            }

            this.mSenseCamera.mRequestedFps = fps;

            return this;
        }

        /**
         * set requested preview size.
         */
        public Builder setRequestedPreviewSize(final int width, final int height) {
            final int max = 1000000;

            if ((width <= 0) || (width > max) || (height <= 0) || (height > max)) {
                throw new IllegalArgumentException("Invalid preview size: " + width + "x" + height);
            }

            this.mSenseCamera.mRequestedPreviewWidth = width;
            this.mSenseCamera.mRequestedPreviewHeight = height;

            return this;
        }

        /**
         * set camera facing.
         */
        public Builder setFacing(final int facing) {
            if ((facing != CAMERA_FACING_BACK) && (facing != CAMERA_FACING_FRONT)) {
                throw new IllegalArgumentException("Invalid camera: " + facing);
            }

            this.mSenseCamera.mFacing = facing;

            return this;
        }

        /**
         * build camera.
         */
        public SenseCamera build() {
            return this.mSenseCamera;
        }
    }

    /**
     * release.
     */
    public void release() {
        synchronized (this.mCameraLock) {
            this.stop();
        }
    }

    public SenseCamera start(final SurfaceProvider surfaceProvider) {
        synchronized (this.mCameraLock) {
            if (this.mCamera != null) {
                return this;
            }

            createCamera(surfaceProvider);
        }
        return this;
    }

    public void stop() {
        synchronized (this.mCameraLock) {
            mBytesToByteBuffer.clear();

            if (this.mCamera != null) {
                this.mCamera.release();
                this.mCamera = null;
            }
        }
    }

    /**
     * get preview size.
     */
    public Size getPreviewSize() {
        return this.mPreviewSize;
    }

    /**
     * get camera facing.
     */
    public int getCameraFacing() {
        return this.mFacing;
    }

    private SenseCamera() {
    }

    private void createCamera(final SurfaceProvider surfaceProvider) {
        final String requestedCameraId = SenseCamera.getIdForRequestedCamera(this.mFacing);

        if (requestedCameraId.isEmpty()) {
            throw new IllegalStateException("Could not find requested camera.");
        }

        CameraKit cameraKit = CameraKit.getInstance(BaseApplication.i().getContext());
        CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl(surfaceProvider);

        cameraKit.createCamera(requestedCameraId, cameraStateCallback, new EventHandler(EventRunner.current()) { });
    }

    private static String getIdForRequestedCamera(final int facing) {
        CameraKit cameraKit = CameraKit.getInstance(BaseApplication.i().getContext());
        String[] cameraList = cameraKit.getCameraIds();

        for (String logicalCameraId : cameraList) {
            int faceType = cameraKit.getCameraInfo(logicalCameraId).getFacingType();
            if (facing == faceType) {
                return logicalCameraId;
            }
        }
        return "";
    }

    private class CameraStateCallbackImpl extends CameraStateCallback {

        private SurfaceProvider surfaceProvider;

        public CameraStateCallbackImpl(SurfaceProvider surfaceProvider) {
            if (surfaceProvider == null) {
                throw new IllegalArgumentException("Create camera filed, surfaceProvider is null");
            }
            this.surfaceProvider = surfaceProvider;
        }

        @Override
        public void onCreated(Camera camera) {
            Surface previewSurface = surfaceProvider
                    .getSurfaceOps()
                    .get()
                    .getSurface();
            Logger.e("==== camera state callback on created previewSurface is null ? " + (previewSurface == null));
            System.out.println("==== camera state callback on created previewSurface is null ? " + (previewSurface == null));
            if (surfaceProvider == null) {
                return;
            }

            CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder();
            cameraConfigBuilder.addSurface(previewSurface);
            camera.configure(cameraConfigBuilder.build());

            mCamera = camera;
        }

        @Override
        public void onConfigured(Camera camera) {



            FrameConfig.Builder framePreviewConfigBuilder = camera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
            framePreviewConfigBuilder.addSurface(surfaceProvider.getSurfaceOps().get().getSurface());

            System.out.println("==== camera state callback on configured..." + surfaceProvider.getSurfaceOps().get().getSurface());

            camera.triggerLoopingCapture(framePreviewConfigBuilder.build());
        }
    }

    private static SizePair selectSizePair(final int facing,
                                           final int desiredWidth,
                                           final int desiredHeight) {
        final List<SizePair> validPreviewSizes = generateValidPreviewSizeList(facing);

        SizePair selectedPair = null;
        int minDiff = Integer.MAX_VALUE;
        for (SizePair sizePair : validPreviewSizes) {
            final Size size = sizePair.previewSize();
            int diff = Math.abs(size.width - desiredWidth) + Math.abs(size.height - desiredHeight);
            if (diff < minDiff) {
                selectedPair = sizePair;
                minDiff = diff;
            }
        }

        return selectedPair;
    }

    private static class SizePair {
        private Size mPreview;
        private Size mPicture;

        public SizePair(final Size previewSize, final Size pictureSize) {
            mPreview = new Size(previewSize.width, previewSize.height);
            if (pictureSize != null) {
                mPicture = new Size(pictureSize.width, pictureSize.height);
            }
        }

        public Size previewSize() {
            return this.mPreview;
        }

        @SuppressWarnings("unused")
        public Size pictureSize() {
            return this.mPicture;
        }
    }

    private static List<SizePair> generateValidPreviewSizeList(final int facing) {
        CameraKit cameraKit = CameraKit.getInstance(BaseApplication.i().getContext());
        String cameraId = getIdForRequestedCamera(facing);
        CameraAbility cameraAbility = cameraKit.getCameraAbility(cameraId);
        List<Size> supportedPreviewSizes = cameraAbility.getSupportedSizes(SurfaceOps.class);
        List<Size> supportedPictureSizes = cameraAbility.getSupportedSizes(ImageFormat.JPEG);
        final List<SizePair> validPreviewSizes = new ArrayList<>();
        for (Size previewSize : supportedPreviewSizes) {
            final float previewAspectRatio = (float) previewSize.width / (float) previewSize.height;

            for (Size pictureSize : supportedPictureSizes) {
                float pictureAspectRatio = (float) pictureSize.width / (float) pictureSize.height;
                if (Math.abs(previewAspectRatio - pictureAspectRatio) < ASPECT_RATIO_TOLERANCE) {
                    validPreviewSizes.add(new SizePair(previewSize, pictureSize));
                    break;
                }
            }
        }
        if (validPreviewSizes.isEmpty()) {
            for (Size previewSize : supportedPreviewSizes) {
                validPreviewSizes.add(new SizePair(previewSize, null));
            }
        }

        return validPreviewSizes;
    }

    // TODO select preview fps range
    private int[] selectPreviewFpsRange(int facing, final float desiredPreviewFps) {
        final int desiredPreviewFpsScaled = (int) (desiredPreviewFps * 1000.0f);

        int[] selectedFpsRange = null;
        int minDiff = Integer.MAX_VALUE;

        return selectedFpsRange;
    }

    private void setRotation(final int cameraId, Camera camera) {

        int rotation = DisplayManager.getInstance()
                .getDefaultDisplay(mContext).get().getRotation();

        int degrees = 0;
        switch (rotation) {
            case 0:
                degrees = 0;
                break;
            case 1:
                degrees = 90;
                break;
            case 2:
                degrees = 180;
                break;
            case 3:
                degrees = 270;
                break;
            default:
                break;
        }

        CameraKit cameraKit = CameraKit.getInstance(
                BaseApplication.i().getContext()
        );
        CameraAbility cameraAbility = cameraKit.getCameraAbility(getIdForRequestedCamera(cameraId));
        Integer orientation = cameraAbility.getPropertyValue(PropertyKey.SENSOR_ORIENTATION);

        int angle;
        int displayAngle;
        if (cameraId == CameraInfo.FacingType.CAMERA_FACING_FRONT) {
            angle = (orientation + degrees) % 360;
            displayAngle = (360 - angle) % 360; // compensate for it being mirrored
        } else { // back-facing
            angle = (orientation - degrees + 360) % 360;
            displayAngle = angle;
        }

        this.mRotation = angle / 90;

        FrameConfig.Builder frameConfigBuilder = camera
                .getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
        frameConfigBuilder.setImageRotation(angle);
    }

    /**
     * get Rotation Degrees.
     * @return Degrees.
     */
    public int getRotationDegrees() {
        return this.mRotation * 90;
    }

    private byte[] createPreviewBuffer(Size previewSize) {
        final int bitsPerPixel = ImageFormat.getBitsNumberPerPixel(ImageFormat.NV21);
        final long sizeInBits = previewSize.height * previewSize.width * bitsPerPixel;
        final int bufferSize = (int) Math.ceil(sizeInBits / 8.0d) + 1;

        final byte[] byteArray = new byte[bufferSize];
        final ByteBuffer buffer = ByteBuffer.wrap(byteArray);

        if (!buffer.hasArray() || (buffer.array() != byteArray)) {
            throw new IllegalStateException("Failed to create valid buffer for camera.");
        }

        this.mBytesToByteBuffer.put(byteArray, buffer);

        return byteArray;
    }
}
